// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/websockets/websocket_deflate_parameters.h"

#include "base/strings/string_number_conversions.h"

namespace net {

namespace {

    const WebSocketDeflater::ContextTakeOverMode kTakeOverContext = WebSocketDeflater::TAKE_OVER_CONTEXT;
    const WebSocketDeflater::ContextTakeOverMode kDoNotTakeOverContext = WebSocketDeflater::DO_NOT_TAKE_OVER_CONTEXT;

    const char kServerNoContextTakeOver[] = "server_no_context_takeover";
    const char kClientNoContextTakeOver[] = "client_no_context_takeover";
    const char kServerMaxWindowBits[] = "server_max_window_bits";
    const char kClientMaxWindowBits[] = "client_max_window_bits";
    const char kExtensionName[] = "permessage-deflate";

    bool GetWindowBits(const std::string& value, int* window_bits)
    {
        return !value.empty() && value[0] != '0' && value.find_first_not_of("0123456789") == std::string::npos && base::StringToInt(value, window_bits);
    }

    bool DuplicateError(const std::string& name, std::string* failure_message)
    {
        *failure_message = "Received duplicate permessage-deflate extension parameter " + name;
        return false;
    }

    bool InvalidError(const std::string& name, std::string* failure_message)
    {
        *failure_message = "Received invalid " + name + " parameter";
        return false;
    }

} // namespace

WebSocketExtension WebSocketDeflateParameters::AsExtension() const
{
    WebSocketExtension e(kExtensionName);

    if (server_context_take_over_mode_ == kDoNotTakeOverContext)
        e.Add(WebSocketExtension::Parameter(kServerNoContextTakeOver));
    if (client_context_take_over_mode_ == kDoNotTakeOverContext)
        e.Add(WebSocketExtension::Parameter(kClientNoContextTakeOver));
    if (is_server_max_window_bits_specified()) {
        DCHECK(server_max_window_bits_.has_value);
        e.Add(WebSocketExtension::Parameter(
            kServerMaxWindowBits, base::IntToString(server_max_window_bits())));
    }
    if (is_client_max_window_bits_specified()) {
        if (has_client_max_window_bits_value()) {
            e.Add(WebSocketExtension::Parameter(
                kClientMaxWindowBits, base::IntToString(client_max_window_bits())));
        } else {
            e.Add(WebSocketExtension::Parameter(kClientMaxWindowBits));
        }
    }

    return e;
}

bool WebSocketDeflateParameters::IsValidAsRequest(std::string*) const
{
    if (server_max_window_bits_.is_specified) {
        DCHECK(server_max_window_bits_.has_value);
        DCHECK(IsValidWindowBits(server_max_window_bits_.bits));
    }
    if (client_max_window_bits_.is_specified && client_max_window_bits_.has_value) {
        DCHECK(IsValidWindowBits(client_max_window_bits_.bits));
    }
    return true;
}

bool WebSocketDeflateParameters::IsValidAsResponse(
    std::string* failure_message) const
{
    if (server_max_window_bits_.is_specified) {
        DCHECK(server_max_window_bits_.has_value);
        DCHECK(IsValidWindowBits(server_max_window_bits_.bits));
    }
    if (client_max_window_bits_.is_specified) {
        if (!client_max_window_bits_.has_value) {
            *failure_message = "client_max_window_bits must have value";
            return false;
        }
        DCHECK(IsValidWindowBits(client_max_window_bits_.bits));
    }

    return true;
}

bool WebSocketDeflateParameters::Initialize(const WebSocketExtension& extension,
    std::string* failure_message)
{
    *this = WebSocketDeflateParameters();

    if (extension.name() != kExtensionName) {
        *failure_message = "extension name doesn't match";
        return false;
    }
    for (const auto& p : extension.parameters()) {
        if (p.name() == kServerNoContextTakeOver) {
            if (server_context_take_over_mode() == kDoNotTakeOverContext)
                return DuplicateError(p.name(), failure_message);
            if (p.HasValue())
                return InvalidError(p.name(), failure_message);
            SetServerNoContextTakeOver();
        } else if (p.name() == kClientNoContextTakeOver) {
            if (client_context_take_over_mode() == kDoNotTakeOverContext)
                return DuplicateError(p.name(), failure_message);
            if (p.HasValue())
                return InvalidError(p.name(), failure_message);
            SetClientNoContextTakeOver();
        } else if (p.name() == kServerMaxWindowBits) {
            if (server_max_window_bits_.is_specified)
                return DuplicateError(p.name(), failure_message);
            int bits;
            if (!GetWindowBits(p.value(), &bits) || !IsValidWindowBits(bits))
                return InvalidError(p.name(), failure_message);
            SetServerMaxWindowBits(bits);
        } else if (p.name() == kClientMaxWindowBits) {
            if (client_max_window_bits_.is_specified)
                return DuplicateError(p.name(), failure_message);
            if (p.value().empty()) {
                SetClientMaxWindowBits();
            } else {
                int bits;
                if (!GetWindowBits(p.value(), &bits) || !IsValidWindowBits(bits))
                    return InvalidError(p.name(), failure_message);
                SetClientMaxWindowBits(bits);
            }
        } else {
            *failure_message = "Received an unexpected permessage-deflate extension parameter";
            return false;
        }
    }
    return true;
}

bool WebSocketDeflateParameters::IsCompatibleWith(
    const WebSocketDeflateParameters& response) const
{
    const auto& request = *this;
    DCHECK(request.IsValidAsRequest());
    DCHECK(response.IsValidAsResponse());

    // server_no_context_take_over
    if (request.server_context_take_over_mode() == kDoNotTakeOverContext && response.server_context_take_over_mode() == kTakeOverContext) {
        return false;
    }

    // No compatibility check is needed for client_no_context_take_over

    // server_max_window_bits
    if (request.server_max_window_bits_.is_specified) {
        DCHECK(request.server_max_window_bits_.has_value);
        if (!response.server_max_window_bits_.is_specified)
            return false;
        DCHECK(response.server_max_window_bits_.has_value);
        if (request.server_max_window_bits_.bits < response.server_max_window_bits_.bits) {
            return false;
        }
    }

    // client_max_window_bits
    if (!request.client_max_window_bits_.is_specified && response.client_max_window_bits_.is_specified) {
        return false;
    }

    return true;
}

} // namespace net
